package com.feinno.urcs.group.servlet;

import com.feinno.sip.header.*;
import com.feinno.sip.header.ims.PAssertedIdentityHeader;
import com.feinno.sip.message.SipRequest;
import com.feinno.sip.message.SipResponse;
import com.feinno.sip.provider.SipProvider;
import com.feinno.sip.transaction.TransactionClient;
import com.feinno.sip.transaction.TransactionClientListener;
import com.feinno.urcs.group.GroupConfiguration;
import com.feinno.urcs.group.GroupConstants;
import com.feinno.urcs.group.GroupError;
import com.feinno.urcs.group.GroupException;
import com.feinno.urcs.group.api.GroupListVersionCacheManager;
import com.feinno.urcs.group.api.GroupService;
import com.feinno.urcs.group.api.GroupSubscriptionManager;
import com.feinno.urcs.group.entity.*;
import com.feinno.urcs.group.interop.FxDataValidateService;
import com.feinno.urcs.group.siputils.GroupHeaderUtils;
import com.feinno.urcs.group.siputils.URIUtils;
import com.feinno.urcs.sip.SipConnector;
import com.feinno.urcs.sip.UrcsSipFactory;
import com.feinno.urcs.sipservlet.*;
import com.feinno.urcs.sipservlet.message.SipMessageBuilder;
import com.feinno.urcs.types.MobileNo;
import com.feinno.util.StringUtils;
import org.helium.framework.annotations.FieldSetter;
import org.helium.framework.annotations.ServiceSetter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sip.InvalidArgumentException;
import javax.sip.address.Address;
import javax.sip.address.SipURI;
import javax.sip.address.URI;
import javax.sip.header.Header;
import javax.sip.message.Request;
import java.text.ParseException;
import java.util.*;

/**
 * Created by honghao on 4/24/15.
 */
public class GroupSubscribeServlet extends SipServlet {
	/**
	 * 日志对象
	 */
	private static final Logger LOGGER = LoggerFactory.getLogger(GroupSubscribeServlet.class);
	/**
	 * 群组服务
	 */
	@ServiceSetter
	private GroupService groupService;
	@ServiceSetter
	private GroupSubscriptionManager subscriptionManager;
	@FieldSetter("group:GroupServlet")
	private GroupServlet servlet;
	@ServiceSetter
	private FxDataValidateService fxDataValidateService;
	@ServiceSetter
	private GroupListVersionCacheManager groupListVersionCacheManager;


	/**
	 * 群组缓存操作
	 */
	@FieldSetter("${CSCF_ADDRESS}")
	private String cscfAddress;

	@Override
	public void process(SipServletRequest servletRequest) throws SipServletException {
		Event event = (Event) servletRequest.getHeader(Event.NAME);
		Expires expires = new Expires();
		int groupId = 0;
		try {
			if (!event.getHeaderValue().toLowerCase().equals("grouplist")) {
				URI uri = GroupHeaderUtils.getFromUriByPAssertedIdentity(servletRequest);
				groupId = GroupHeaderUtils.parseGroupId(servletRequest);
				groupService.check(MobileNo.parseFrom(URIUtils.getMobileNo(uri)), GroupIndex.toConferenceUri(groupId));
				expires.setExpires(servletRequest.getExpires().getExpires());
			}
		} catch (InvalidArgumentException e) {
			servletRequest.sendResponse(500);
			return;
		} catch (GroupException e) {
			try {
				GroupError error = e.getError();
				Warning warning = new Warning();
				warning.setAgent(error.getAgent());
				warning.setCode(error.getWarningCode());
				warning.setText(error.getText());
				SipResponse response = servletRequest.createResponse(error.getSipCode());
				response.setHeader(warning);
				servletRequest.sendResponse(response);
			} catch (ParseException ex) {
				LOGGER.info("GroupSubscribeServlet catch ParseException {}", ex);
				throw new RuntimeException(ex);
			} catch (InvalidArgumentException ex) {
				LOGGER.info("GroupSubscribeServlet catch InvalidArgumentException {}", ex);
				throw new RuntimeException(ex);
			}
			return;
		} catch (ParseException e) {
			servletRequest.sendResponse(400);
			return;
		} catch (Exception e) {
			servletRequest.sendResponse(500);
			return;
		}

		SipResponse response = servletRequest.createResponse(200);
		SipMessageBuilder builder = new SipMessageBuilder(response);
		response = builder.contact(GroupConfiguration.INSTANCE.getGroupPrefix() + groupId, GroupConfiguration.INSTANCE.getGroupDomain(), GroupConstants.G_3GPP_ICSI_REF_SESSION_KEY_VALUE, true)
				.supported("100rel,timer")
				.allow("INVITE,ACK,BYE,CANCEL,UPDATE,OPTIONS,REGISTER,INFO,MESSAGE,SUBSCRIBE,NOTIFY,REFER")
				.setHeader(expires)
				.toSipResponse();

//		try {
		switch (event.getHeaderValue().toLowerCase()) {
			case "grouplist":
				GroupListSubXml verXml = new GroupListSubXml();
				try {
					verXml.parseXmlFrom(new String(servletRequest.getRawContent(), "UTF-8"));
				} catch (Exception e) {
					LOGGER.warn("subGroupList xml error : {}", new String(servletRequest.getRawContent()));
					response = servletRequest.createResponse(400);
				}
				int version = 0;
				try {
					version = Integer.parseInt(verXml.getVersion());
				} catch (Exception ex) {
					version = 0;
				}
				servletRequest.sendResponse(response);
				URI uri = ((PAssertedIdentityHeader) servletRequest.getHeader(PAssertedIdentityHeader.NAME)).getAddress().getURI();
				MobileNo fromMobileNo = MobileNo.parseFrom(URIUtils.getMobileNo(uri));
				fxDataValidateService.loadData(fromMobileNo);
				/** grouplist为持久订阅，需增加或更新订阅点 */
				subGroupList(servletRequest, "grouplist", version);
				break;
			case "groupinfo":
				servletRequest.sendResponse(response);
				/** groupinfo为一次订阅，无需增加或更新订阅点 */
				subGroupInfo(servletRequest, "groupinfo");
				break;
			case "conference":
				servletRequest.sendResponse(response);
				/** groupinfo为持久订阅，需增加或更新订阅点 */
				subConference(servletRequest, "conference");
				break;
		}
//		} catch (GroupException e) {
//			servletRequest.createResponse(e.getError().getSipCode());
//		} catch (Exception e) {
//			LOGGER.error("GroupSubscribeServlet.process occur error : {}", e);
//			servletRequest.createResponse(500);
//		}
	}

	private void subGroupInfo(SipServletRequest sipServletRequest, String notityEvent) {
		/** NOTIFY下发逻辑 */
		int groupId = 0;
		try {
			groupId = GroupHeaderUtils.parseGroupId(sipServletRequest);
			GroupIndex index = groupService.getGroupIndex(groupId);
			GroupInfo groupInfo = groupService.getGroupInfo(index);
			sendGroupNotifyToMember(groupInfo, sipServletRequest, notityEvent, 0);
		} catch (Exception ex) {
			LOGGER.error("subGroupInfo error, groupid : {}, excepiton : {}", groupId, ex);
		}
	}

	private void subGroupList(SipServletRequest sipServletRequest, String notityEvent, int version) {
		/** NOTIFY下发逻辑 */
		URI uri = ((PAssertedIdentityHeader) sipServletRequest.getHeader(PAssertedIdentityHeader.NAME)).getAddress().getURI();
		try {
			MobileNo fromMobileNo = MobileNo.parseFrom(URIUtils.getMobileNo(uri));
			List<Integer> groupList = groupService.getGroupList(fromMobileNo);
			sendGroupListNotifyToMember(groupList, sipServletRequest, notityEvent, version);
			GroupListSubscription subscription = new GroupListSubscription();
			subscription.setCallId(sipServletRequest.getCallIdHeader().getCallId());
			subscription.setCSeq((int) sipServletRequest.getCseq().getSeqNumber() + 1);
			subscription.setFromTag(sipServletRequest.getToHeader().getTag());
			subscription.setToTag(sipServletRequest.getFromHeader().getTag());
			Calendar calendar = Calendar.getInstance();
			calendar.add(Calendar.SECOND, GroupConfiguration.INSTANCE.getSubscriptionExpires());
			subscription.setExpires(calendar.getTime());
			Iterator<RecordRoute> rr = sipServletRequest.getOriginalRequest().getRecordRouteHeaders().getHeaderList().listIterator();
			List<String> route = new LinkedList<>();
			while (rr.hasNext()) {
				RecordRoute nextRR = rr.next();
				Route tmp = new Route();
				tmp.setAddress(nextRR.getAddress());
				tmp.setParameters(nextRR.getParameters());
				((LinkedList<String>) route).addLast(tmp.toString().replace("\r\n", "\n"));
			}
			subscription.setRoute(route);
			String contact = sipServletRequest.getContactHeader().getAddress().toString();
			subscription.setContact(contact);
//			subscriptionManager.subscribeGroupList(fromMobileNo, subscription);
		} catch (Exception ex) {
			LOGGER.error("subGroupList error, uri : {}, excepiton : {}", uri, ex);
		}
	}

	private void subConference(SipServletRequest servletRequest, String notityEvent) {
		int groupId = 0;
		try {
			MobileNo fromMobileNo = MobileNo.parseFrom(URIUtils.getMobileNo(GroupHeaderUtils.getFromUriByPAssertedIdentity(servletRequest)));
			/** NOTIFY下发逻辑 */
			groupId = GroupHeaderUtils.parseGroupId(servletRequest);
			GroupIndex index = groupService.getGroupIndex(groupId);
			GroupInfo groupInfo = groupService.getGroupInfo(index);
			Expires expiresHeader = (Expires) servletRequest.getHeader(SIPHeaderNames.EXPIRES);
			int expires;
			if (null == expiresHeader || StringUtils.isNullOrEmpty(expiresHeader.getValue().trim())) {
				expires = GroupConfiguration.INSTANCE.getSubscriptionExpires();
			} else {
				int i = Integer.parseInt(expiresHeader.getValue().trim());
				expires = ((i == 0) ? GroupConfiguration.INSTANCE.getSubscriptionExpires() : i);
			}
			sendGroupNotifyToMember(groupInfo, servletRequest, notityEvent, expires);
			GroupMemberSubscription subscription = new GroupMemberSubscription();
			subscription.setUserMobile(fromMobileNo);
			String callId = servletRequest.getCallIdHeader().getCallId();
			subscription.setCallId(callId);
			subscription.setCSeq((int) servletRequest.getCseq().getSeqNumber() + 1);
			subscription.setFromTag(servletRequest.getToHeader().getTag());
			subscription.setToTag(servletRequest.getFromHeader().getTag());
			Calendar calendar = Calendar.getInstance();
			calendar.add(Calendar.SECOND, GroupConfiguration.INSTANCE.getSubscriptionExpires());
			subscription.setExpires(expires);
			subscription.setSubType(GroupSubscribeType.AD_GROUP);
			Iterator<RecordRoute> rr = servletRequest.getOriginalRequest().getRecordRouteHeaders().getHeaderList().listIterator();
			List<String> route = new LinkedList<>();
			while (rr.hasNext()) {
				RecordRoute nextRR = rr.next();
				Route tmp = new Route();
				tmp.setAddress(nextRR.getAddress());
				tmp.setParameters(nextRR.getParameters());
				((LinkedList<String>) route).addLast(tmp.toString().replace("\r\n", "\n"));
			}
			subscription.setRoute(route);

			String contact = servletRequest.getContactHeader().getAddress().toString();
			subscription.setContact(contact);
			subscriptionManager.subscribeConference(groupId, fromMobileNo, subscription);
		} catch (Exception ex) {
			LOGGER.error("subConference error, groupid : {}, excepiton : {}", groupId, ex);
		}
	}

	private void sendGroupNotifyToMember(GroupInfo groupInfo, SipServletRequest servletRequest, String notityEvent, int expired) {
		SipRequest notify = null;
		try {
			notify = createNotify(groupInfo.getGroupId(), servletRequest, expired, notityEvent);
			ContentType contentType = (ContentType) UrcsSipFactory.getHeaderFactory().createContentTypeHeader("application", "conference-info+xml");
//			byte[] xml = null;
			byte[] xml = createContectXML(groupInfo, expired);
			SipMessageBuilder builder = new SipMessageBuilder(notify);
			if (xml.length >= 13 * 1024) {
				builder.gzipContent(xml, "application", "conference-info+xml");
				notify = builder.toSipReqeust();
			} else {
				notify.setContent(xml, contentType);
			}
			SipConnector connector = servletRequest.getConnector();
			// TODO dialog是否需要new
			SipServletRequest notifySr = SipServletRequestFactory.createRequest(notify, connector, null);
			SipServletFuture future = notifySr.sendRequest();
			future.setListener(new TransactionClientListener() {
				@Override
				public void onTransProvisionalResponse(TransactionClient transactionClient, SipResponse sipResponse) {
					LOGGER.info("onTransProvisionalResponse  ");
					//TODO
				}

				@Override
				public void onTransSuccessResponse(TransactionClient transactionClient, SipResponse sipResponse) {
					LOGGER.info("onTransSuccessResponse  ");
				}

				@Override
				public void onTransFailureResponse(TransactionClient transactionClient, SipResponse sipResponse) {
					LOGGER.info("onTransFailureResponse  ");
					//TODO
				}

				@Override
				public void onTransTimeout(TransactionClient transactionClient) {
					LOGGER.info("onTransTimeout  ");
					//TODO
				}
			});
			// TODO update subscriptions
		} catch (Exception e) {
			LOGGER.error("sendGroupNotify", e);
		}
	}

	private void sendGroupListNotifyToMember(List<Integer> groupList, SipServletRequest sipServletRequest, String notityEvent, int version) {
		try {
			SipRequest notify = createNotify(0, sipServletRequest, GroupConfiguration.INSTANCE.getSubscriptionExpires(), "grouplist");
			ContentType contentType = (ContentType) UrcsSipFactory.getHeaderFactory().createContentTypeHeader("application", "conference-info+xml");
			PAssertedIdentityHeader pai = (PAssertedIdentityHeader) sipServletRequest.getHeader(PAssertedIdentityHeader.NAME);
			byte[] xml = createGroupListNotifyXml(groupList, pai.getAddress().getURI(), version);
			SipMessageBuilder builder = new SipMessageBuilder(notify);
			if (xml.length >= 13 * 1024) {
				builder.gzipContent(xml, "application", "conference-info+xml");
				notify = builder.toSipReqeust();
			} else {
				notify.setContent(xml, contentType);
			}
			// TODO
//			GroupServlet servlet = (GroupServlet) Bootstrap.INSTANCE.loadServlet("group-servlet.xml");
			SipServletFuture future = SipServletRequestFactory.createRequest(notify, sipServletRequest.getConnector(), servlet).sendRequest();
			future.setListener(new TransactionClientListener() {
				@Override
				public void onTransProvisionalResponse(TransactionClient transactionClient, SipResponse sipResponse) {
					LOGGER.info("onTransProvisionalResponse");
					//TODO
				}

				@Override
				public void onTransSuccessResponse(TransactionClient transactionClient, SipResponse sipResponse) {
					LOGGER.info("onTransSuccessResponse success");

					//TODO

				}

				@Override
				public void onTransFailureResponse(TransactionClient transactionClient, SipResponse sipResponse) {
					LOGGER.info("onTransFailureResponse");
					LOGGER.info(sipResponse.toString());
					//TODO
				}

				@Override
				public void onTransTimeout(TransactionClient transactionClient) {
					LOGGER.info("transactionClient");
					//TODO
				}
			});
		} catch (Exception e) {
			LOGGER.error(String.format("GroupSubscribeServlet.sendGroupListNotifyToMember error groupList=%s  ", groupList), e);

		}

	}

	private byte[] createGroupListNotifyXml(List<Integer> groupList, URI rcsUri, int version) throws Exception {
		String uri = rcsUri.toString();
		String mobileNo = URIUtils.getMobileNo(rcsUri);
		int curGrpListVer = 0;
		if (version == 0) {
			groupListVersionCacheManager.resetGrpListVer(MobileNo.parseFrom(mobileNo));
			curGrpListVer = groupListVersionCacheManager.incGrpListVer(MobileNo.parseFrom(mobileNo));
		} else {
			curGrpListVer = groupListVersionCacheManager.getCurGrpListVer(MobileNo.parseFrom(mobileNo));
		}
		GroupListNotifyInfo notifyInfo = new GroupListNotifyInfo();
		notifyInfo.setEntity(uri);
		notifyInfo.setXmlns("urn:ietf:params:xml:ns:conference-info");
		notifyInfo.setState("list");
		GroupListNotifyInfo.ConferenceList conferenceList = new GroupListNotifyInfo.ConferenceList();
		List<GroupListNotifyInfo.GroupListInfo> groupListInfos = new ArrayList<GroupListNotifyInfo.GroupListInfo>();
		if (version == 0 || version != curGrpListVer) {
			for (int groupId : groupList) {
				GroupListNotifyInfo.GroupListInfo groupListInfo = new GroupListNotifyInfo.GroupListInfo();
				GroupIndex index = groupService.getGroupIndex(groupId);
				if (index == null) {
					LOGGER.warn(String.format("getGroupIndex error , groupId : %s", groupId));
					continue;
				}
				groupListInfo.setEntity(index.getConferenceUri());
				// TODO 此处应该调用群列表服务器，当前先走groupservice
				GroupInfo groupInfo = groupService.getGroupInfo(index);
				String conversationId = groupInfo.getConversationId();
				if (StringUtils.isNullOrEmpty(conversationId)) {
					continue;
				}
				groupListInfo.setConversationid(conversationId);
				groupListInfo.setName(groupInfo.getSubject());
				groupListInfos.add(groupListInfo);
			}
		}
		GroupListNotifyInfo.GroupListVer groupListVer = new GroupListNotifyInfo.GroupListVer();
		groupListVer.setVersion(String.valueOf(curGrpListVer));
		conferenceList.setGroupListVer(groupListVer);
		conferenceList.setGrouplistInfos(groupListInfos);
		notifyInfo.setConferenceList(conferenceList);
		return notifyInfo.toXmlByteArray();
	}

	private byte[] createContectXML(GroupInfo groupInfo, long expired) throws ParseException {
		List<GroupMember> members = groupInfo.getMembers();
		/** conference-description */
		GroupChatMemberNotifyInfo.ConferenceDescription conferenceDescription = new GroupChatMemberNotifyInfo.ConferenceDescription();
		if (!StringUtils.isNullOrEmpty(groupInfo.getSubject()))
			conferenceDescription.setSubject(groupInfo.getSubject());

		conferenceDescription.setMaximum_user_count(String.valueOf(groupInfo.getLimitMemberCount()));
		/** conference-state */
		GroupChatMemberNotifyInfo.ConferenceState conferenceState = new GroupChatMemberNotifyInfo.ConferenceState();
		conferenceState.setActive(true);
		conferenceState.setLocked(false);
		GroupChatMemberNotifyInfo notifyInfo = new GroupChatMemberNotifyInfo();
		/** user-policy */
		GroupChatMemberNotifyInfo.UserPolicy userPolicy = new GroupChatMemberNotifyInfo.UserPolicy();
		userPolicy.setOfflinesm(1); // TODO
		notifyInfo.setUserPolicy(userPolicy);
		/** users */
		List<GroupChatMemberNotifyInfo.MemberInfo> memberInfos = new ArrayList<GroupChatMemberNotifyInfo.MemberInfo>();
		int count = 0;
		for (GroupMember member : members) {
			if (member.getIdentity() != 1 && member.getIdentity() != 3) {
				continue;
			}
			count++;
			GroupChatMemberNotifyInfo.MemberInfo memberInfo = new GroupChatMemberNotifyInfo.MemberInfo();
			SipURI sipURL = UrcsSipFactory.getAddressFactory().createSipURI(member.getMobileNo().toString(), GroupConfiguration.INSTANCE.getDomain());
			memberInfo.setEntity(sipURL.toString());
			GroupChatMemberNotifyInfo.Roles roles = new GroupChatMemberNotifyInfo.Roles();
			if (member.getRole() == GroupMemberRole.CHAIRMAN) {
				roles.setEntry("chairman");
			} else {
				roles.setEntry("participant");
			}
			memberInfo.setRoles(roles);
			GroupChatMemberNotifyInfo.UserEndpoint endpoint = new GroupChatMemberNotifyInfo.UserEndpoint();
			endpoint.setEntity(sipURL.toString());
			if (!StringUtils.isNullOrEmpty(member.getDisplayname()))
				endpoint.setDisplayname(member.getDisplayname());
			endpoint.setStatus(member.getStatus().toString().toLowerCase());
			GroupChatMemberNotifyInfo.SupportedMedias supportedMedias = new GroupChatMemberNotifyInfo.SupportedMedias();
			supportedMedias.setXmlns("dconf:conference-info");
			endpoint.setSupportedMedias(supportedMedias);
			memberInfo.setEndpoint(endpoint);
			memberInfo.setEtype("gpmanage");
			memberInfo.setState("partial");
			memberInfos.add(memberInfo);
		}
		conferenceState.setUserCount(String.valueOf(count));
		notifyInfo.setUsers(memberInfos);
		notifyInfo.setConferenceDescription(conferenceDescription);
		notifyInfo.setConferenceState(conferenceState);


		notifyInfo.setVersion(String.valueOf(expired));
		GroupIndex index = new GroupIndex(groupInfo.getGroupId(), groupInfo.getLogicalPool());
		notifyInfo.setEntity(index.getConferenceUri());
		return notifyInfo.toXmlByteArray();
	}

	private SipRequest createNotify(int groupId, SipServletRequest servletRequest, int expired, String notityEvent) throws ParseException, InvalidArgumentException {
		String host = GroupConfiguration.INSTANCE.getGroupPrefix() + groupId;
		SipURI groupUri = UrcsSipFactory.getAddressFactory().createSipURI(GroupConfiguration.INSTANCE.getGroupPrefix() + groupId, GroupConfiguration.INSTANCE.getGroupDomain());
		SipProvider provider = servletRequest.getConnector().getSipProvider();
		Address from = servletRequest.getTo().getAddress();
		Address to = servletRequest.getFrom().getAddress();
		List<Route> route = new LinkedList<>();
		Iterator<RecordRoute> rr = servletRequest.getOriginalRequest().getRecordRouteHeaders().getHeaderList().listIterator();
		while (rr.hasNext()) {
			RecordRoute nextRR = rr.next();
			Route tmp = new Route();
			tmp.setAddress(nextRR.getAddress());
			tmp.setParameters(nextRR.getParameters());
			((LinkedList<Route>) route).addLast(tmp);
		}
		int expires = 0;
		if (!notityEvent.equalsIgnoreCase("grouplist")) {
			expires = expired;
		}
		SipMessageBuilder notifyBuilder = new SipMessageBuilder(UrcsSipFactory.getMessageFactory().createRequest(provider, Request.NOTIFY, to, from))
				.from(servletRequest.getTo().getAddress().getURI())
				.to(servletRequest.getFrom().getAddress().getURI())
				.fromTag(servletRequest.getTo().getTag())
				.toTag(servletRequest.getFrom().getTag())
				.requestUri(servletRequest.getContactHeader().getAddress().getURI())
				.callId(servletRequest.getCallIdHeader().getCallId())
				.cSeq(servletRequest.getCseq().getSeqNumber() + 1, Request.NOTIFY)
				.event(notityEvent)
				.allow("INVITE,UPDATE,ACK,CANCEL,BYE,NOTIFY,OPTIONS,MESSAGE,REFER")
				.maxForwards(70)
				.supported("timer,recipient-list-invite")
				.userAgent("Feinno-serv/CMCC")
				.allowEvents(((Event) servletRequest.getHeader(Event.NAME)).getEventType())
				.subscriptionState(expires)
				.contact(groupUri.getUser(), groupUri.getHost(), GroupConstants.G_3GPP_ICSI_REF_SESSION_KEY_VALUE, true)
				.pai(servletRequest.getRequestURI());
		if (route.size() > 0) {
			route.forEach(notifyBuilder::addHeader);
		}
		notifyBuilder.removeHeader(SIPHeaderNames.EXPIRES);
		if (groupId == 0) {
			notifyBuilder.contact((Contact) servletRequest.getHeader(Contact.NAME));
		} else {
			notifyBuilder.contact(host, GroupConfiguration.INSTANCE.getGroupDomain(), "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"", true);
		}
		return notifyBuilder.toSipReqeust();
	}
}
